feat(session_waiter): allow session_waiter handler to return MessageEventResult - #9551
Open
Hiraeth-Wave wants to merge 4 commits into
Open
feat(session_waiter): allow session_waiter handler to return MessageEventResult#9551Hiraeth-Wave wants to merge 4 commits into
Hiraeth-Wave wants to merge 4 commits into
Conversation
Let session_waiter handlers return a MessageEventResult instead of forcing them to call event.send(). The returned result is yielded back into the pipeline by handle_session_control_agent, so it goes through the full ResultDecorateStage (reply-with-mention, reply-with-quote, segmented reply, TTS, text-to-image, etc.) and RespondStage like any normal reply. Backward compatible: handlers that return None keep the original stop_event behavior.
📄 Knowledge reviewDosu skipped reviewing this PR because your organization has used its |
When stop_event() was called after RespondStage had already sent and cleared the result (e.g. in handle_session_control_agent after yielding a MessageEventResult), it fabricated an empty MessageEventResult(chain=[]) and re-attached it to the event. The scheduler's onion model then ran RespondStage again on this empty result, producing a spurious second 'Prepare to send - (empty)' log (and triggering after_message_sent hooks a second time). Now stop_event()/continue_event() only set _force_stopped; they no longer create a result when none exists. is_stopped() already checks _force_stopped first, so stop semantics are unchanged. All stage entry points (ResultDecorateStage, RespondStage) guard with 'if result is None: return', so the empty second invocation is eliminated. Updated the two existing unit tests that asserted the old fabricating behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolve #9548.
Modifications / 改动点
核心改动(3 文件):
astrbot/core/utils/session_waiter.py:SessionWaiter.trigger返回 handler 的返回值(原为丢弃),返回类型None→Any。astrbot/builtin_stars/astrbot/main.py:handle_session_control_agent改为 async generator——当 trigger 返回MessageEventResult时yield它,借助 scheduler 的洋葱模型自动执行ResultDecorateStage和RespondStage(发送);否则保持原stop_event行为。astrbot/core/platform/astr_message_event.py:修复stop_event()/continue_event()在_result is None时凭空创建空MessageEventResult(chain=[])的行为,改为只设置_force_stopped标志。Bug 修复说明(
stop_event空发送):在改
handle_session_control_agent为 async generator 后,实测发现每次 waiter 返回MessageEventResult后RespondStage会触发两次:一次发送实际内容,一次发送空内容(日志Prepare to send - (空))。根因是三个机制叠加:
yield result→RespondStage发送实际内容后调clear_result()→event._result = None。event.stop_event()发现_result is None,凭空造一个空MessageEventResult(chain=[])挂回去(旧代码第 347 行self.set_result(MessageEventResult().stop_event()))。ProcessStage结束后继续遍历后续 stage,RespondStage看到非 None 的空 result,再次进入发送分支(chain 为空不会真正 send,但会打印日志并触发OnAfterMessageSentEvent钩子)。is_stopped()第一行就检查_force_stopped,根本不需要那个空 result;ResultDecorateStage/RespondStage入口都有if result is None: return保护。因此移除空 result 创建是安全的,且彻底消除了空发送。测试(3 文件,9 个用例):
tests/unit/test_session_waiter.py:覆盖 trigger 在 handler 返回MessageEventResult/None/ 抛异常 / 无对应 session 四种情况下的返回值。tests/unit/test_session_control_agent.py:覆盖handle_session_control_agent在 trigger 返回MessageEventResult(yield + stop)/None(无 yield + stop)/ 无匹配 session(无操作)三种分支。tests/unit/test_astr_message_event.py:更新原有的test_stop_event_creates_result_if_none/test_continue_event_creates_result_if_none两个用例,断言新行为(不再创建空 result,_result保持None,is_stopped()靠_force_stopped返回正确值)。工作机制:
call_handler检测到 handler 是 async generator 且yield出MessageEventResult时,会自动event.set_result(ret)再向上yield,触发 scheduler 递归执行后续 stage。因此 waiter handler 只需return event.plain_result(...),回复即走完整装饰流程。向后兼容:
event.send()且不 return 的,handler 返回None,走原stop_event路径,行为完全不变。stop_event()的语义不变(is_stopped()仍正确返回 True),只是不再产生空 result 副作用。经全局排查,所有stop_event()调用点要么在 stage 内部(stop 后 pipeline break),要么有if result is None: return保护,无代码依赖「stop 后get_result()一定非 None」。文档更新:更新会话控制文档,使用新调用方法,增加相关提示。此外顺带更新了过时的
import。插件侧用法示例
改前(装饰功能失效):
改后(装饰功能生效):
Screenshots or Test Results / 运行截图或测试结果
$ uv run pytest tests/unit/test_session_control_agent.py tests/unit/test_session_waiter.py tests/unit/test_astr_message_event.py -q 85 passed, 1 warning in 13.11s用于测试此改动的插件:Hiraeth-Wave/astrbot_plugin_test/tree/test/astrbot-feat-9551,可下载源码压缩包后自行安装测试。
Checklist / 检查清单
requirements.txt和pyproject.toml文件相应位置。